/* 
 * 
 * Copyright (c) 2007 John Wells
 * Copyright (c) 2010 Photonic Games

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */

// Short-cuts
using EntityKeyValuePair = System.Collections.Generic.KeyValuePair<string, object>;

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline.Processors;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Graphics.PackedVector;

using GameLevel;
using GameLevel.ContentPipeline.Leadwerks14;
using GameLevel.ContentPipeline.Serialization;

namespace GameLevel.ContentPipeline.Leadwerks14
{
    [ContentProcessor(DisplayName = "Level (.3dw)")]
    public class Leadwerks14LevelProcessor : ContentProcessor<Leadwerks14Content, LevelContent>
    {
        #region Fields
        // Private variables
        private Leadwerks14Content _content;
        private ContentProcessorContext _context;
        private LevelContent _output = new LevelContent();
        
        // These caches are kept to make processing quicker
        private SortedList<string, SurfaceDocument> _cachedSurfaces = new SortedList<string, SurfaceDocument>();
        private SortedList<string, SurfaceDocument> _cachedLightmapSurfaces = new SortedList<string, SurfaceDocument>();
        
        // Other unimportant private variables
        private Random _random = new Random();
        
        #endregion

        public override LevelContent Process(Leadwerks14Content input, ContentProcessorContext context)
        {
            // Note: Uncomment the next line to debug this processor
            //System.Diagnostics.Debugger.Launch();

            // Store these values for the process methods
            this._content = input;
            this._context = context;

            // These calls must happen in this order to process the .3dw file
            this.LoadSurfaces();
            this.BuildEntities();
            this.BuildModels();
            this.BuildHeadNode();
            this.BuildTerrain();
            this.BuildCollisonBoxes();
            
            this._output.Lightmaps = this._content.Lightmaps;

            return this._output;
        }

        #region Lightmapping Methods

        Rasterizer tr = new Rasterizer();

        public class VectorBuffer
        {
            public Vector3[] buffer;
            int width, height;

            public VectorBuffer (int width, int height) {
                this.width = width;
                this.height = height;
                buffer = new Vector3[width * height];
            }
        }

        VectorBuffer[] lightDirectionBuffer;
        const int ldbwidth = 512;

        public Texture2DContent CreateLightingMap(VectorBuffer VB, int width, int height)
        {

            //System.Diagnostics.Debugger.Launch();

            byte[] arrayData = new byte[width * height * 4];

            for (int i = 0; i < height; i++)
            {
                for (int j = 0; j < width; j++)
                {

                    // Values are -1 to 1 and must be changed to 0 to 256
                    arrayData[((i * width) + j) * 4] = (byte) ((VB.buffer[(i*width)+j].X ) * 255f);
                    arrayData[(((i * width) + j) * 4) + 1] = (byte)((VB.buffer[(i * width) + j].Y) * 255f);
                    arrayData[(((i * width) + j) * 4) + 2] = (byte)((VB.buffer[(i * width) + j].Z) * 255f);
                    arrayData[(((i * width) + j) * 4) + 3] = 0xff;

                    if (VB.buffer[(i * width) + j].X != 0 || 
                        VB.buffer[(i * width) + j].Y != 0 || 
                        VB.buffer[(i * width) + j].Z != 0)
                    {
                        int test = 0;
                        test++;
                    }
                }
            }

            // Load the sprite texture into memory.
            BitmapContent bitMap = new PixelBitmapContent<Color>(width, height);
            bitMap.SetPixelData(arrayData);

            Texture2DContent textMap = new Texture2DContent();
            textMap.Mipmaps.Add(bitMap);

            return (textMap);
        }

        private void MapTriangleLights(int LightmapIndex, BrushVertex[] list)
        {
            Rasterizer.Triangle[] triangles = new Rasterizer.Triangle[list.Length / 3];
            for (int i = 0; i < list.Length / 3; i++)
            {
                triangles[i] = new Rasterizer.Triangle();
                triangles[i].index[0] = (uint)(i * 3);
                triangles[i].index[1] = (uint)(i * 3) + 1;
                triangles[i].index[2] = (uint)(i * 3) + 2;
            }
            tr.CalculateTangents(list.Length, list, list.Length / 3, triangles);

            for (int i = 0; i < list.Length / 3; i++)
            {
                Vector2 lm0 = list[(i * 3)].LightmapCoordinate * ldbwidth;
                Vector2 lm1 = list[(i * 3) + 1].LightmapCoordinate * ldbwidth;
                Vector2 lm2 = list[(i * 3) + 2].LightmapCoordinate * ldbwidth;

                Vector3[] frameBuffer = new Vector3[16];

                if (LightmapIndex < 0) return;

                tr.SetFrameBuffer(lightDirectionBuffer[LightmapIndex], ldbwidth, ldbwidth);
                tr.SetTangents(list[(i * 3)].Normal, list[(i * 3)].Binormal, list[(i * 3)].Tangent);
                tr.DrawTriangle(lights, list[(i * 3)].Position, lm0.X, lm0.Y,
                        list[(i * 3) + 1].Position, lm1.X, lm1.Y,
                        list[(i * 3) + 2].Position, lm2.X, lm2.Y);
            }
        }

        class BrushModel
        {
            public String name;
            public Vector3 origin;
            public List<Brush> brushes = new List<Brush>();
        }


        private void BuildHeadNode()
        {
            Dictionary<String, BrushModel> brushModels = new Dictionary<string, BrushModel>();

            // What a boring level....
            if (this._content.Brushes == null)
                return;

            lightDirectionBuffer = new VectorBuffer[_content.Lightmaps.Count];
           
            for (int i = 0; i < _content.Lightmaps.Count; i++)
            {
                lightDirectionBuffer[i] = new VectorBuffer(ldbwidth, ldbwidth);
            }

            // Add all visible faces to a list
            List<BrushFace> faces = new List<BrushFace>();

            foreach (Brush brush in this._content.Brushes)
            {
                if (brush.isTrigger) continue;

                if (brush.isModel)
                {
                    if (String.IsNullOrEmpty(brush.Name)) continue;

                    if (brushModels.ContainsKey(brush.Name))
                    {
                        brushModels[brush.Name].brushes.Add(brush);
                    }
                    else
                    {
                        Vector3 origin = Vector3.Zero;

                        foreach (KeyValuePair<string, string> keyPair in brush.Items)
                        {
                            if (keyPair.Key.ToLower() == "origin")
                            {
                                string value = keyPair.Value.ToLower();
                                origin = ConvertToVector3(value);
                                origin.Z *= -1;
                            }
                        }
                        BrushModel bm = new BrushModel();
                        bm.brushes.Add(brush);
                        bm.name = brush.Name;
                        bm.origin = origin;
                        brushModels.Add(bm.name,bm);
                        }
                    continue;
                }

               
                foreach (BrushFace face in brush.VisibleFaces)
                {
                    if (face.Surface.NoDraw)
                        continue;

                    // This is a final check to make sure we're playing with faces that have
                    // valid surface documents attached to them...
                    if (face.Surface == null)
                    {
                        this._context.Logger.LogMessage("Skipping face with missing surface definition");
                        continue;
                    }

                    faces.Add(face);
                }
            }

            this._output.Geometry = BuildMeshLeafNode(faces);
            this._output.GeometryModels = new List<GeometryModelContent>();

            //System.Diagnostics.Debugger.Launch();

            foreach (String key in brushModels.Keys)
            {
                Vector3 min, max;
                BrushModel bm = brushModels[key];
                List<BrushFace> modelFaces = new List<BrushFace>();
                GeometryModelContent geometryModel = new GeometryModelContent(key,Vector3.Zero,Vector3.Zero,bm.brushes[0].Items);
                foreach (Brush brush in bm.brushes)
                {
                    min = max = brush.VisibleFaces[0].Vertices[0].Position - bm.origin;

                    foreach (BrushFace face in brush.VisibleFaces)
                    {
                        if (face.Surface.NoDraw)
                            continue;

                        // This is a final check to make sure we're playing with faces that have
                        // valid surface documents attached to them...
                        if (face.Surface == null)
                        {
                            this._context.Logger.LogMessage("Skipping face with missing surface definition");
                            continue;
                        }

                        modelFaces.Add(face);

                        for (int j = 0;j<face.Vertices.Count;j++) 
                        {
                            Vector3 Position = face.Vertices[j].Position - bm.origin;
                            face.Vertices[j].SetPosition(Position);
                            

                            if (Position.X < min.X) min.X = Position.X;
                            if (Position.Y < min.Y) min.Y = Position.Y;
                            if (Position.Z < min.Z) min.Z = Position.Z;
                            if (Position.X > max.X) max.X = Position.X;
                            if (Position.Y > max.Y) max.Y = Position.Y;
                            if (Position.Z > max.Z) max.Z = Position.Z;
                        }

                    }

                    geometryModel.bbox = new BoundingBox(min, max);
                    geometryModel.position = bm.origin;
                }
                geometryModel.models = BuildMeshLeafNode(modelFaces);
                this._output.GeometryModels.Add(geometryModel);
            }

            for (int i = 0; i < _content.Lightmaps.Count; i++)
            {
                this._output.LightDirectionMaps.Add(CreateLightingMap(lightDirectionBuffer[i], ldbwidth, ldbwidth));
            }

            



        }

        
        private ModelContent[] BuildMeshLeafNode(IEnumerable<BrushFace> faces)
        {
            //System.Diagnostics.Debugger.Launch();

            List<ModelContent> models = new List<ModelContent>();

            // Find the unique brushes that created these faces
            List<Brush> brushes = new List<Brush>();
            foreach (BrushFace face in faces)
                if (!brushes.Contains(face.Brush))
                    brushes.Add(face.Brush);


            // Collect all the vertices for this leaf into a sorted-by-material list
            Dictionary<SurfaceDocument, List<BrushFace>> sortedFaces = new Dictionary<SurfaceDocument, List<BrushFace>>();
            foreach (BrushFace face in faces)
            {
                // If we have not yet accumulated faces for this material, create a list for them
                if (!sortedFaces.ContainsKey(face.Surface))
                    sortedFaces.Add(face.Surface, new List<BrushFace>());

                sortedFaces[face.Surface].Add(face);
            }

            // Turn the sorted sets of faces into drawable triangle list primitives
            foreach (SurfaceDocument surface in sortedFaces.Keys)
            {

                MeshBuilder builder = MeshBuilder.StartMesh("bspmesh");
                List<BrushVertex> vertices = new List<BrushVertex>();

                //System.Diagnostics.Debugger.Launch();

                foreach (BrushFace face in sortedFaces[surface])
                {
                    // Brush faces are fans -> Convert to list style
                    BrushVertex[] fan = face.Vertices.ToArray();
                    BrushVertex[] list = TriangleHelper.ConvertFanToList(fan);
                    vertices.AddRange(list);

                    MapTriangleLights(surface.LightmapIndex, list);
                }

                for (int i = 0; i < vertices.Count; i++)
                {
                    builder.CreatePosition(vertices[i].Position);
                }

                EffectMaterialContent material = new EffectMaterialContent();

                material.Effect = new ExternalReference<EffectContent>(surface.Effect);
                foreach (SurfaceDocument.TextureInstanceElement texture in surface.TextureInstances)
                {
                    ExternalReference<TextureContent> textureName = new ExternalReference<TextureContent>(texture.Filename);
                    material.Textures.Add(texture.Parameter, textureName);
                }

                
                builder.SetMaterial(material);


                int texCoordId = builder.CreateVertexChannel<Vector2>(
                                            VertexChannelNames.TextureCoordinate(0));
                int lmCoordId = builder.CreateVertexChannel<Vector2>(
                                            VertexChannelNames.TextureCoordinate(1));


                for (int i = 0; i < vertices.Count; i++)
                {

                    builder.SetVertexChannelData(texCoordId, vertices[i].TextureCoordinate);
                    builder.SetVertexChannelData(lmCoordId, vertices[i].LightmapCoordinate);

                    builder.AddTriangleVertex(i);
                }

                MeshContent bspMesh = builder.FinishMesh();
                MeshHelper.OptimizeForCache(bspMesh);

                // Process all the geometry in the mesh.

                //create a dictionary to hold the processor parameter
                OpaqueDataDictionary parameters = new OpaqueDataDictionary();

                //add several parameters to the dictionary
                if (!String.IsNullOrEmpty(surface.Effect))
                {
                    parameters.Add("Shader", surface.Effect);
                }

                
                bspMesh.Identity = this._content.Identity;
                ModelContent model = _context.Convert<MeshContent, ModelContent>(bspMesh,
                                                                  typeof(ModelProcessor).Name, parameters);

                LevelModelParams param = new LevelModelParams();
                param.lightMapIndex = surface.LightmapIndex;
                param.transparent = surface.Transparent;
                param.hidden = surface.Hidden;
                param.name = surface.Name;
                model.Tag = param;
                models.Add(model);
            }

            return models.ToArray();
        }
        #endregion

        private string findKey(string key, KeyValuePair<string, string>[] keyValues)
        {
            foreach (KeyValuePair<string, string> keyVal in keyValues)
            {
                if (keyVal.Key.ToLower() == key.ToLower()) return ((string)keyVal.Value);
            }
            return null;
        }

        private string findKey(string key, KeyValuePair<string, object>[] keyValues)
        {
            foreach (KeyValuePair<string, object> keyVal in keyValues)
            {
                if (keyVal.Key.ToLower() == key.ToLower()) return ((string)keyVal.Value);
            }
            return null;
        }

        public class LightEntity
        {
            public Vector3 position;
            public Vector4 color;
            public float intensity;
            public float falloff;
        }

        public List<LightEntity> lights = new List<LightEntity>();

        #region Entity Methods
        /// <summary>This method converts the 3dw style entities to Level-style.</summary>
        private void BuildEntities()
        {
            if (this._content.Entities != null)
                foreach (Entity entity in this._content.Entities)
                {
                    bool isDecal = false;
                    // Create the output variables that will be added to the results list
                    Level.Entity newEntity = new Level.Entity();
                    List<EntityKeyValuePair> items = new List<EntityKeyValuePair>();

                    // Position is unchanged
                    newEntity.Position = entity.Position;

                    // Copy items (optionally in the future change the type of the value object to reflect color, position, etc)
                    foreach (EntityKeyValuePair item in entity.Items)
                    {
                        items.Add(new EntityKeyValuePair(item.Key, item.Value));
                        string valueStr = (string)item.Value;

                        if (item.Key.ToLower() == "classname")
                        {
                            
                            if (valueStr.ToLower() == "decal") isDecal = true;
                        }

                        if (isDecal && item.Key.ToLower() == "texture")
                        {
                            
                            string assetName = Path.GetFileNameWithoutExtension(valueStr);
                            this.BuildAsset<TextureContent, TextureContent>(valueStr, typeof(TextureProcessor).Name, assetName);
                        }

                        if ((item.Key.ToLower() == "classname" &&  valueStr.ToLower() == "light")||
                            (item.Key.ToLower() == "classname" &&  valueStr.ToLower() == "spotlight")) 

                        {
                           
                            LightEntity light = new LightEntity();
                            light.position = entity.Position;

                            string colorStr = findKey("color", entity.Items);
                            string intensityStr = findKey("intensity", entity.Items);
                            string rangeStr = findKey("range", entity.Items);

                           
                            light.color = new Vector4(ConvertToVector3(colorStr),255);
                            light.intensity = Convert.ToSingle(intensityStr);
                            light.falloff = Convert.ToSingle(rangeStr);

                            lights.Add(light);

                        }
                    }

                    // Assign the items and add it to the output list
                    newEntity.Items = items.ToArray();
                    this._output.Entities.Add(newEntity);
                }
        } 
        #endregion

        #region Model Methods

        private Vector3 ConvertToVector3(string str)
        {
            int offset = str.IndexOf(" ");
            Vector3 vect = Vector3.Zero;

            vect.X = Convert.ToSingle(str.Substring(0, str.IndexOf(" ")));
            str = str.Substring(str.IndexOf(" ") + 1, str.Length - str.IndexOf(" ") - 1);
            vect.Y = Convert.ToSingle(str.Substring(0, str.IndexOf(" ")));
            str = str.Substring(str.IndexOf(" ") + 1, str.Length - str.IndexOf(" ") - 1);
            vect.Z = Convert.ToSingle(str);

            return vect;
        }
        void AddVertex(MeshBuilder builder, int texCoordId, int lmCoordId, float texCoordScale, int w, int x, int y)
        {


            builder.SetVertexChannelData(texCoordId, new Vector2(x, y) * texCoordScale);
            builder.SetVertexChannelData(lmCoordId, new Vector2(x, w-y) / w);

            builder.AddTriangleVertex(x + y * w);
        }

        private void BuildTerrain() 
        {
            foreach (Terrain terrain in this._content.Terrains)
            {
                TerrainContent terrainContent = new TerrainContent();

                MeshBuilder builder = MeshBuilder.StartMesh(terrain.terrainName);
                float terrainScale = terrain.width / terrain.resolution;

                // Create the terrain vertices.
                for (int y = 0; y <= terrain.resolution; y++) 
                {
                    for (int x = 0; x <= terrain.resolution; x++) 
                    {
                        Vector3 position;

                        // position the vertices so that the heightfield is centered
                        // around x=0,z=0
                        position.X = terrainScale * (x - ((terrain.resolution) / 2.0f));
                        position.Z = terrainScale * (((terrain.resolution) / 2.0f) - y);

                        position.Y = terrain.heightMap[x * (terrain.resolution+1) + y] * terrain.height;

                        builder.CreatePosition(position);
                    }
                }

                EffectMaterialContent material = new EffectMaterialContent();

                terrainContent.layers = new TerrainContent.Layer[terrain.layers.Length];
                for (int i=0;i<terrain.layers.Length;i++)  
                {
                    Terrain.Layer layer = terrain.layers[i];
                    terrainContent.layers[i] = new TerrainContent.Layer();
                    terrainContent.layers[i].layerName = terrain.layers[i].material.Filename;
                    // Create a material, and point it at our terrain texture.

                    //string directory = Path.GetDirectoryName(input.Identity.SourceFilename);
                    //string texture = Path.Combine(directory, terrainTextureFilename);

                    // Load the sprite texture into memory.
                    BitmapContent bitMap = new PixelBitmapContent<Alpha8>(terrain.resolution, terrain.resolution);
                    bitMap.SetPixelData(terrain.layers[i].alpha);

                    terrainContent.layers[i].alpha = new Texture2DContent();
                    terrainContent.layers[i].alpha.Mipmaps.Add(bitMap);

                    if (i == 0)
                    {
                        SurfaceDocument surface = LoadSurface(terrain.layers[i].material.Filename, terrain.layers[i].material.Filename);
                        material.Effect = new ExternalReference<EffectContent>(surface.Effect);
                        foreach (SurfaceDocument.TextureInstanceElement texture in surface.TextureInstances)
                        {
                            ExternalReference<TextureContent> textureName = new ExternalReference<TextureContent>(texture.Filename);

                            material.Textures.Add(texture.Parameter, textureName);
                        }
                    }
                    else
                    {
                        SurfaceDocument surface = LoadSurface(terrain.layers[i].material.Filename, terrain.layers[i].material.Filename);

                        ExternalReference<TextureContent> textureName = new ExternalReference<TextureContent>(surface.TextureInstances[0].Filename);
                        String layerName = "Layer_" + i.ToString();
                        material.Textures.Add(layerName, textureName);
                    }
                }


                builder.SetMaterial(material);

                // Create a vertex channel for holding texture coordinates.
                int texCoordId = builder.CreateVertexChannel<Vector2>(
                                                VertexChannelNames.TextureCoordinate(0));

                // Create a vertex channel for holding texture coordinates.
                int lmCoordId = builder.CreateVertexChannel<Vector2>(
                                                VertexChannelNames.TextureCoordinate(1));

                float texCoordScale = (float)terrain.width / (float)(128 * terrain.resolution);

                // Create the individual triangles that make up our terrain.
                for (int y = 0; y < terrain.resolution; y++)
                {
                    for (int x = 0; x < terrain.resolution; x++)
                    {
                        AddVertex(builder, texCoordId, lmCoordId, texCoordScale, terrain.resolution + 1, x, y);
                        AddVertex(builder, texCoordId, lmCoordId, texCoordScale, terrain.resolution + 1, x + 1, y);
                        AddVertex(builder, texCoordId, lmCoordId, texCoordScale, terrain.resolution + 1, x + 1, y + 1);

                        AddVertex(builder, texCoordId, lmCoordId, texCoordScale, terrain.resolution + 1, x, y);
                        AddVertex(builder, texCoordId, lmCoordId, texCoordScale, terrain.resolution + 1, x + 1, y + 1);
                        AddVertex(builder, texCoordId, lmCoordId, texCoordScale, terrain.resolution + 1, x, y + 1);
                    }
                }

                // Chain to the ModelProcessor so it can convert the mesh we just generated.
                MeshContent terrainMesh = builder.FinishMesh();

                terrainContent.model = this._context.Convert<MeshContent, ModelContent>(terrainMesh,
                                                                  "ModelProcessor");
                terrainContent.heightMap = terrain.heightMap;
                terrainContent.height = terrain.height;
                terrainContent.width = terrain.width;
                terrainContent.position = terrain.position;
                terrainContent.resolution = terrain.resolution;
                terrainContent.flags = terrain.flags;

                if ((terrain.flags & 1) != 0)
                {
                    // Load the sprite texture into memory.
                    BitmapContent bitMap = new PixelBitmapContent<Color>(terrain.resolution, terrain.resolution);
                    bitMap.SetPixelData(terrain.lightMap);
                    terrainContent.lightMap = new Texture2DContent();
                    terrainContent.lightMap.Mipmaps.Add(bitMap);
                }

                // generate information about the height map, and attach it to the finished
                // model's tag.
                //model.Tag = new HeightMapInfoContent(heightfield, terrainScale,
                //    terrainBumpiness);

                this._output.Terrains.Add(terrainContent);
            }
            
        }
    

        /// <summary>This method converts the 3dw style entities to Level-style.</summary>
        private void BuildModels()
        {
            //System.Diagnostics.Debugger.Launch();
            Dictionary<string, bool> ModelTextures = new Dictionary<string, bool>();

            //System.Diagnostics.Debugger.Launch();
            Dictionary<string, bool> Models = new Dictionary<string, bool>();
            string[] additionalModelNames;

            if (this._content.MeshReferences.Count != 0)
            {
                foreach (MeshReference meshRef in this._content.MeshReferences)
                {
                    // All materials should be located in the same directory as the content being processed
                    string path = Path.GetDirectoryName(this._content.Identity.SourceFilename);

                    // The surface file is named after the material in the 3dw file
                    string filename = Path.Combine(path, meshRef.groupName + "/" + meshRef.objectName + ".xml");

                    // See if this file exists
                    if (File.Exists(filename))
                    {
                        // Load the surface file (Push, Log, Load, Pop)
                        this._context.Logger.PushFile(filename);
                        this._context.Logger.LogMessage("Loading surface {0} ", filename);
                        ModelCommandEntry modelCmd = ModelCommandEntry.FromFile(Path.Combine(path, filename));
                        this._context.AddDependency(filename);
                        this._context.Logger.PopFile();

                        ModelCommand cmd = new ModelCommand();
                        cmd.name = meshRef.groupName + "/" + meshRef.objectName;
                        cmd.effect = modelCmd.Effect;
                        if (modelCmd.Effect == null) cmd.effect = "";
                       
                        cmd.animated = modelCmd.Animated;

                        if (!String.IsNullOrEmpty(modelCmd.Min) && !String.IsNullOrEmpty(modelCmd.Max))
                        {
                            Vector3 min = ConvertToVector3(modelCmd.Min);
                            Vector3 max = ConvertToVector3(modelCmd.Max);
                            cmd.bbox = new BoundingBox(min, max);
                        }
                        else cmd.bbox = new BoundingBox(Vector3.Zero, Vector3.Zero);

                        meshRef.cmd = cmd;
                        this._output.ModelCmds.Add(cmd);
                    }
                }
            }

            if (this._content.Meshes.Count != 0)
            {
                foreach (Mesh mesh in this._content.Meshes)
                {
                    // Create the output variables that will be added to the results list
                    string modelName = mesh.meshRef.groupName + "/" + mesh.meshRef.objectName;
                    LevelModel newMdl = new LevelModel(modelName, mesh.position, mesh.rotation, mesh.keys);
                    OpaqueDataDictionary parameters = null;
                    this._output.Models.Add(newMdl);

                    bool animatedModelType;
                    if (mesh.meshRef.cmd != null && mesh.meshRef.cmd.animated) animatedModelType = true;
                    else if (findKey("animated", mesh.keys) != null) animatedModelType = true;
                    else animatedModelType = false;

                    if (animatedModelType) newMdl.type = LevelModel.ModelShaderType.SkinnedModelType;
                    else if (findKey("Glow", mesh.keys) != null) newMdl.type = LevelModel.ModelShaderType.GlowModelType;
                    else newMdl.type = LevelModel.ModelShaderType.DefaultModelType;

                    if (!Models.ContainsKey(modelName))
                    {

                        if ((mesh.meshRef.cmd != null) && (mesh.meshRef.cmd.textures != null) &&
                            (mesh.meshRef.cmd.textures.Length > 0))
                        {
                            for (int i = 0; i < mesh.meshRef.cmd.textures.Length; i++)
                            {
                                if (ModelTextures.ContainsKey(mesh.meshRef.cmd.textures[i])) continue;

                                string txtPathName = mesh.meshRef.groupName + "/" + mesh.meshRef.cmd.textures[i];
                                string assetName = Path.GetFileNameWithoutExtension(txtPathName);
                                this.BuildAsset<TextureContent, TextureContent>(txtPathName, typeof(TextureProcessor).Name, assetName);
                                mesh.meshRef.cmd.textures[i] = assetName;

                                ModelTextures.Add(mesh.meshRef.cmd.textures[i], true);
                            }
                        }


                        ExternalReference<NodeContent> modelFile = new ExternalReference<NodeContent>(modelName + ".x");
                        if ((mesh.meshRef.cmd != null) && !String.IsNullOrEmpty(mesh.meshRef.cmd.effect))
                        {
                            parameters = new OpaqueDataDictionary();
                            parameters.Add("Shader", mesh.meshRef.cmd.effect);
                        }

                        this._context.BuildAsset<NodeContent, ModelContent>(modelFile, typeof(ModelProcessor).Name, parameters, null, modelName);
                           
                        Models.Add(modelName, true);
                    }
                }
            }
        }
        #endregion

        #region Surface Methods
        private void LoadSurfaces()
        {
            // Skip when we have no brushes - boring level
            if (this._content.Brushes == null)
                return;

            // Load all surfaces for all faces of all brushes
            foreach (Brush brush in this._content.Brushes) {

                brush.isTrigger = false; 
                foreach (KeyValuePair<string, string> keyPair in brush.Items) {
                    if (keyPair.Key.ToLower() == "brushtype")
                    {
                        string value = keyPair.Value.ToLower();
                        if (value == "trigger")
                        {
                            this._output.Triggers.Add(new LevelTriggerContent(brush));
                            brush.isTrigger = true;
                        }
                        else if (value == "ladder")
                        {
                            this._output.Triggers.Add(new LevelTriggerContent(brush));
                            brush.isTrigger = true;
                        }
                        else if (value == "model")
                        {
                            this._output.Triggers.Add(new LevelTriggerContent(brush));
                            brush.isModel = true;
                        }
                    }

                    else if (keyPair.Key.ToLower() == "name") brush.Name = keyPair.Value;
                }
       
                if (brush.isTrigger) continue;
                //System.Diagnostics.Debugger.Launch();

                if (brush.isModel && String.IsNullOrEmpty(brush.Name))
                {
                    this._context.Logger.LogMessage("Skipping unnamed model brush");
                    continue;
                }

                foreach (BrushFace face in brush.AllFaces)
                {
                    
                    // Hidden faces don't have materials to load...
                    if (face.Material == null || string.IsNullOrEmpty(face.Material.Filename))
                        continue;

                    // Load the surface name using the materials' filename
                    string surfaceName = face.Material.Filename;
                    int lightmapIndex;
                    if (face.LightmapIndex != null)
                        lightmapIndex= (int)face.LightmapIndex;
                    else lightmapIndex = -1;

                    face.Name = brush.Name;

                    SurfaceDocument surface;
                    String key = surfaceName + "&" + lightmapIndex.ToString();
                    if (!String.IsNullOrEmpty(face.Name)) 
                         key = key + "&" + face.Name;

                    surface = this.LoadSurface(surfaceName,key);
                    surface.Name = face.Name;

                    // Set this so the remaining processing has handy access to it
                    face.Surface = surface;
                    face.Surface.LightmapIndex = lightmapIndex;
                
                }
            }
        }

        /// <summary>Caches the surface data and builds related textures and effects</summary>
        private SurfaceDocument LoadSurface(string surfaceName, string key)
        {
            if (this._cachedLightmapSurfaces.ContainsKey(key))
                return this._cachedLightmapSurfaces[key];
            else if (this._cachedSurfaces.ContainsKey(surfaceName))
            {
                SurfaceDocument surface = new SurfaceDocument();
                surface.Copy(this._cachedSurfaces[surfaceName]);

                this._cachedLightmapSurfaces.Add(key, surface);
                return surface;
            }
            else
            {
                // All materials should be located in the same directory as the content being processed
                string path = Path.GetDirectoryName(this._content.Identity.SourceFilename);

                // The surface file is named after the material in the 3dw file
                string filename = Path.Combine(path, Path.ChangeExtension(surfaceName, "xml"));

                // Asset that this file exists
                if (!File.Exists(filename))
                    throw new InvalidContentException(string.Format("Surface file not found: {0}", filename), new ContentIdentity(filename));

                // Load the surface file (Push, Log, Load, Pop)
                this._context.Logger.PushFile(filename);
                this._context.Logger.LogMessage("Loading surface {0} ", filename);
                SurfaceDocument surface = SurfaceDocument.FromFile(Path.Combine(path, filename));
                this._context.AddDependency(filename);
                this._context.Logger.PopFile();
                

                // Keep the loaded material data in the cache
                this._cachedSurfaces.Add(surfaceName, surface);
                this._cachedLightmapSurfaces.Add(key, surface);

                return surface;
            }
        }

        private void BuildAsset<T, K>(string filename, string processorName, string assetName)
        {
            this._context.Logger.PushFile(filename);

            // Build and process the effect asset
            ExternalReference<T> sourceAsset = new ExternalReference<T>(filename);
            try
            {
                ExternalReference<K> builtAsset = this._context.BuildAsset<T, K>(sourceAsset, processorName, null, null, assetName);
                
                // Add an external dependency to this file
                this._context.AddDependency(builtAsset.Filename);
                this._context.Logger.PopFile();
            }
            catch (PipelineException ex)
            {
                // XNA Does this crazy thing where any existing XNB file disallows other XNB files of the same name
                // from being built - Why oh why could it not check the existing XNB and say, oh look - This is the
                // same asset - I guess I'll just accept that I've already done this. Or hey, it could also just
                // do what the developer says! If I say THIS IS THE ASSET NAME, gosh darn it believe me! If that
                // name already exists, well for holy sake just give me the benefit of the doubt that I know what
                // I'm building! If nothing else, don't fail on me without giving me a) a way to see if this will
                // fail before trying it or b) A specific exception so I don't have to do the crappy locale-specific
                // message-text checking that I'm doing below. Otherwise, I love this whole XNA thing. Thanks!
                if (ex.Message == string.Format("More than one output asset is named \"{0}\".\n You must explicitly specify asset names to resolve the conflict.", assetName))
                    // Do nothing - this exception can safely be ignored because I know what I'm doing
                    this._context.Logger.PopFile();
                else
                    throw;
            }
        }
        #endregion

        #region Collision Methods

        private void BuildCollisonBoxes()
        {
            foreach (Brush brush in this._content.Brushes)
            {
                if (brush.isTrigger) continue;
                if (brush.isModel) continue;
                string brushName = "";

                bool noclip = false;
                foreach (KeyValuePair<string, string> keyPair in brush.Items)
                {
                    // Check to see if this brush should be ignored for collision testing
                    if ((keyPair.Key.ToLower() == "noclip"))
                    {
                        noclip = true;
                        break;
                    }
                    else if ((keyPair.Key.ToLower() == "name"))
                    {
                        brushName = keyPair.Value;
                    }
                }
                if (noclip) continue;

                bool first = true;
                Vector3 minPos = Vector3.Zero, maxPos = Vector3.Zero;
                List<CollisionTriangle> cts = new List<CollisionTriangle>();
                
                foreach (BrushFace face in brush.VisibleFaces)
                {
                    int vertexCount = 0;
                    Vector3[] ctvs = new Vector3[face.Vertices.Count];

                    if (!face.Surface.Clip)
                        continue;

                    foreach (BrushVertex vertex in face.Vertices)
                    {
                        ctvs[vertexCount++] = vertex.Position;

                        if (first)
                        {
                            minPos = maxPos = vertex.Position;
                            first = false;
                        }
                        else
                        {
                            if (minPos.X > vertex.Position.X) minPos.X = vertex.Position.X;
                            if (minPos.Y > vertex.Position.Y) minPos.Y = vertex.Position.Y;
                            if (minPos.Z > vertex.Position.Z) minPos.Z = vertex.Position.Z;

                            if (maxPos.X < vertex.Position.X) maxPos.X = vertex.Position.X;
                            if (maxPos.Y < vertex.Position.Y) maxPos.Y = vertex.Position.Y;
                            if (maxPos.Z < vertex.Position.Z) maxPos.Z = vertex.Position.Z;
                        }
                    }

                    if (face.Vertices.Count >= 3)
                    {
                        cts.Add(new CollisionTriangle(ctvs[0], ctvs[1], ctvs[2], face.Vertices.Count));
                    }
                }

                if (minPos.Z < -256 && maxPos.Z > -256 && cts.Count > 0) 
                    _output.CollisionBoxes.Add(new CollisionBox(brushName, minPos, maxPos, cts.ToArray()));
            }
        }

        
        #endregion
    }
}